/*
 *   Copyright 2012-present OSBI Ltd
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

// Packages
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import $ from 'jquery';
import { Grid, Row, Col } from 'react-flexbox-grid';
import {
  Button,
  Card,
  Classes,
  Dialog,
  InputGroup,
  Intent,
  Position,
  Tree
} from '@blueprintjs/core';
import BlockUi from 'react-block-ui';
import { Debounce } from 'react-throttle';

// Services
import { RepositoryService } from '../../../services';

// Actions
import { actionCreators } from '../../../actions';

// UI
import { ErrorMessage, Loading, WarningAlert } from '../../UI';

// Alerts
import OverwriteAlert from './OverwriteAlert';

// Utils
import { Saiku, Settings } from '../../../utils';

// Constants
const { QUERY_PROPERTY_DATA } = Settings;
const REPOSITORY_OBJECTS_HEIGHT = 350;
const ERROR_MSG = 'Error fetching data from repository';

class SaveQueryDialog extends Component {
  _isMounted = false;

  state = {
    treeNodesData: [],
    treeNodesStaticData: [],
    selectedPath: '',
    repositoryObjectsHeight: REPOSITORY_OBJECTS_HEIGHT,
    isDisabledActionBtn: true,
    isLoadingActionBtn: false,
    isOpenSelectFolder: false,
    isOpenFileNameNotExistsAlert: false,
    isOpenOverwriteAlert: false,
    loading: true,
    error: false,
    errorMsg: ERROR_MSG
  };

  componentWillMount() {
    const height = $('body').height() / 2 + $('body').height() / 6;

    if (height > REPOSITORY_OBJECTS_HEIGHT) {
      this.setState({ repositoryObjectsHeight: REPOSITORY_OBJECTS_HEIGHT });
    } else {
      this.setState({ repositoryObjectsHeight: height });
    }
  }

  componentDidMount() {
    this.callApiGetRepositories();
  }

  componentWillUnmount() {
    this._isMounted = false;
    RepositoryService.cancelRequest();
  }

  callApiGetRepositories = () => {
    this._isMounted = true;
    this.setState({
      loading: true,
      error: false
    });

    RepositoryService.getRepositories()
      .then(res => {
        if (this._isMounted && res.status === 200) {
          const { data } = res;

          this.setState(
            {
              treeNodesData: this.deepCloneReplace(data, {
                repoObjects: 'childNodes',
                name: 'label'
              }),
              loading: false
            },
            () => {
              this.setState({ treeNodesStaticData: this.state.treeNodesData });
            }
          );
        } else {
          if (this._isMounted) {
            this.setState({
              isLoadingActionBtn: false,
              loading: false,
              error: true
            });
          }
        }
      })
      .catch(error => {
        if (this._isMounted) {
          this.setState({
            isLoadingActionBtn: false,
            loading: false,
            error: true
          });
        }
      });
  };

  deepCloneReplace(obj, replaceMap) {
    const clone = {};

    Object.keys(obj).forEach(key => {
      let newKey = key;

      if (key in replaceMap) {
        if (replaceMap.hasOwnProperty(key)) {
          newKey = replaceMap[key];
        }
      }

      clone[newKey] =
        typeof obj[key] === 'object'
          ? this.deepCloneReplace(obj[key], replaceMap)
          : obj[key];

      if (clone[key] === 'FOLDER') {
        clone['hasCaret'] = true;
        clone['icon'] = 'folder-close';
      } else if (clone[key] === 'FILE') {
        clone['icon'] = 'document';
      }
    });

    return Array.isArray(obj) && obj.length
      ? (clone.length = obj.length) && Array.from(clone)
      : Array.isArray(obj)
      ? Array.from(obj)
      : clone;
  }

  searchTreeNode = (nodeData, matchingLabel) => {
    if (
      nodeData.label.toLowerCase().indexOf(matchingLabel.toLowerCase()) !== -1
    ) {
      return nodeData;
    } else if (nodeData.childNodes !== null && nodeData.type === 'FOLDER') {
      let result = null;

      for (let i = 0; result === null && i < nodeData.childNodes.length; i++) {
        result = this.searchTreeNode(nodeData.childNodes[i], matchingLabel);
      }

      return result;
    }

    return null;
  };

  handleChangeSearch = event => {
    const { value } = event.target;
    const newTreeNodesData = this.state.treeNodesStaticData
      .map(nodeData => this.searchTreeNode(nodeData, value))
      .filter((nodeData, i, self) => nodeData && self.indexOf(nodeData) === i);

    this.setState({ treeNodesData: newTreeNodesData });
  };

  forEachNode(nodes, callback) {
    if (nodes == null) {
      return;
    }

    for (const node of nodes) {
      callback(node);
      this.forEachNode(node.childNodes, callback);
    }
  }

  getFiles(repositoryFiles) {
    const paths = [];

    Object.keys(repositoryFiles).forEach(key => {
      if (repositoryFiles[key].type === 'FOLDER') {
        paths.push.apply(
          paths,
          this.getFiles(repositoryFiles[key].repoObjects)
        );
      } else {
        paths.push(repositoryFiles[key].path);
      }
    });

    return paths;
  }

  handleFolderState(nodeData, isExpanded = false, iconName = 'folder-close') {
    nodeData.isExpanded = isExpanded;
    nodeData.icon = iconName;
    this.setState(this.state);
  }

  checkFileNameExists(file) {
    return file.indexOf('/', file.length - 1) === -1;
  }

  handleNodeClick = (nodeData, nodePath, event) => {
    const originallySelected = nodeData.isSelected;
    const { icon, path } = nodeData || null;

    if (!event.shiftKey) {
      this.forEachNode(this.state.treeNodesData, n => (n.isSelected = false));
    }

    nodeData.isSelected =
      originallySelected === null ? true : !originallySelected;
    this.setState(this.state);

    if (path) {
      this.setState({
        selectedPath:
          icon === 'folder-close' || icon === 'folder-open' ? `${path}/` : path,
        isDisabledActionBtn: false
      });
    } else {
      if (!nodeData.isExpanded) {
        this.handleFolderState(nodeData, true, 'folder-open');
      } else {
        this.handleFolderState(nodeData);
      }
    }
  };

  handleNodeCollapse = nodeData => {
    this.handleFolderState(nodeData);
  };

  handleNodeExpand = nodeData => {
    this.handleFolderState(nodeData, true, 'folder-open');
  };

  handleSaveQuery = () => {
    const { selectedPath } = this.state;

    this.setState({ isLoadingActionBtn: true });

    if (this.checkFileNameExists(selectedPath)) {
      RepositoryService.getRepositories()
        .then(res => {
          if (res.status === 200) {
            const { data } = res;
            const paths = [];

            paths.push.apply(paths, this.getFiles(data));

            if (paths.indexOf(selectedPath) !== -1) {
              this.handleOverwriteAlert();
            } else {
              this.copyToRepository();
            }
          } else {
            this.setState({
              isLoadingActionBtn: false,
              loading: false,
              error: true
            });
          }
        })
        .catch(error => {
          this.setState({
            isLoadingActionBtn: false,
            loading: false,
            error: true
          });
        });
    } else {
      this.setState({
        isLoadingActionBtn: false,
        isOpenFileNameNotExistsAlert: true
      });
    }
  };

  handleFileNameNotExistsAlert = () => {
    this.setState(prevState => ({
      isOpenFileNameNotExistsAlert: !prevState.isOpenFileNameNotExistsAlert
    }));
  };

  handleSelectFolderAlert = () => {
    this.setState(prevState => ({
      isOpenSelectFolder: !prevState.isOpenSelectFolder
    }));
  };

  handleOverwriteAlert = (event, closeAlertOnly = false) => {
    this.setState(
      prevState => ({
        isOpenOverwriteAlert: !prevState.isOpenOverwriteAlert
      }),
      () => {
        if (!this.state.isOpenOverwriteAlert && !closeAlertOnly) {
          this.setState({ isLoadingActionBtn: false });
        }
      }
    );
  };

  copyToRepository = async () => {
    const { workspace, setQueryProperty, saveQuery, onClose } = this.props;
    const { selectedPath } = this.state;
    let file = selectedPath;

    file =
      file.length > 6 && file.indexOf('.saiku') === file.length - 6
        ? file
        : `${file}.saiku`;

    workspace.file = file;

    await setQueryProperty({
      [QUERY_PROPERTY_DATA]: JSON.stringify(workspace)
    });

    const query = JSON.stringify(this.props.query);

    RepositoryService.saveQuery(selectedPath, file, query)
      .then(res => {
        if (res.status === 200) {
          saveQuery({ file });

          Saiku.toasts(Position.TOP_RIGHT).show({
            icon: 'tick',
            intent: Intent.SUCCESS,
            message: 'Saved!'
          });

          onClose();
        } else {
          this.setState({
            isLoadingActionBtn: false,
            isOpenSelectFolder: true
          });
        }
      })
      .catch(error => {
        this.setState({
          isLoadingActionBtn: false,
          isOpenSelectFolder: true
        });
      });
  };

  renderTree() {
    const {
      treeNodesData,
      selectedPath,
      repositoryObjectsHeight,
      isLoadingActionBtn
    } = this.state;

    return (
      <BlockUi tag="div" blocking={isLoadingActionBtn}>
        <Grid fluid>
          <Row>
            <Col xs>
              <Debounce time="100" handler="onChange">
                <InputGroup
                  leftIcon="search"
                  placeholder="Search..."
                  onChange={this.handleChangeSearch}
                />
              </Debounce>
            </Col>
          </Row>
          <Row className="m-t-10">
            <Col xs>
              <Card
                style={{ height: repositoryObjectsHeight, overflowY: 'auto' }}
              >
                <Tree
                  contents={treeNodesData}
                  onNodeClick={this.handleNodeClick}
                  onNodeCollapse={this.handleNodeCollapse}
                  onNodeExpand={this.handleNodeExpand}
                />
              </Card>
            </Col>
          </Row>
          <Row className="m-t-10">
            <Col xs>
              <InputGroup
                leftIcon="double-chevron-right"
                value={selectedPath}
                onChange={event =>
                  this.setState({ selectedPath: event.target.value })
                }
              />
            </Col>
          </Row>
        </Grid>
      </BlockUi>
    );
  }

  render() {
    const { onClose } = this.props;
    const {
      isDisabledActionBtn,
      isLoadingActionBtn,
      isOpenSelectFolder,
      isOpenFileNameNotExistsAlert,
      isOpenOverwriteAlert,
      loading,
      error,
      errorMsg
    } = this.state;

    return (
      <Fragment>
        <Dialog
          title="Save Query"
          icon="floppy-disk"
          canOutsideClickClose={false}
          isOpen={true}
          onClose={onClose}
        >
          <div className={Classes.DIALOG_BODY}>
            {loading ? (
              <Loading className="m-t-20" size={30} center />
            ) : error ? (
              <ErrorMessage
                text={errorMsg}
                callApi={this.callApiGetRepositories}
              />
            ) : (
              this.renderTree()
            )}
          </div>

          {!loading && !error && (
            <div className={Classes.DIALOG_FOOTER}>
              <div className={Classes.DIALOG_FOOTER_ACTIONS}>
                <Button
                  text="Save"
                  intent={Intent.DANGER}
                  disabled={isDisabledActionBtn}
                  loading={isLoadingActionBtn}
                  onClick={this.handleSaveQuery}
                />
                <Button text="Close" onClick={onClose} />
              </div>
            </div>
          )}
        </Dialog>

        {isOpenSelectFolder && (
          <WarningAlert
            message="You need to select a folder!"
            onCancel={this.handleSelectFolderAlert}
            onConfirm={this.handleSelectFolderAlert}
          />
        )}

        {isOpenFileNameNotExistsAlert && (
          <WarningAlert
            message="You need to enter a name!"
            onCancel={this.handleFileNameNotExistsAlert}
            onConfirm={this.handleFileNameNotExistsAlert}
          />
        )}

        {isOpenOverwriteAlert && (
          <OverwriteAlert
            onCancel={this.handleOverwriteAlert}
            onConfirm={this.copyToRepository}
          />
        )}
      </Fragment>
    );
  }
}

SaveQueryDialog.propTypes = {
  query: PropTypes.object.isRequired,
  workspace: PropTypes.object.isRequired,
  setQueryProperty: PropTypes.func.isRequired,
  saveQuery: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  ...state.query,
  ...state.workspace
});

const mapDispatchToProps = dispatch => ({
  setQueryProperty: data => dispatch(actionCreators.setQueryProperty(data)),
  saveQuery: data => dispatch(actionCreators.saveQuery(data))
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SaveQueryDialog);
